import java.io.*;
import java.net.*;
import java.util.*;


public class Peer implements Runnable {
	//Data members
	private UDPAdapter _udpAdapter = null; //UDP socket
	private TCPListener _tcpListener = null; //Listener to TCP messages
	private PictureManager _pictureManager = null;
	private Updater _updater = null;
	private Handler _handler = null;
	
	private final String DB_FILE_NAME = "db.dat"; //Database file name in hard drive
	
	//Owner record in DB
	Record _owner = null; 
	private String _name = null;
	private String _ip = null;
	private int _portTCP;
	private int _portUDP;
	private String _phone = null;
	private String _email = null;
	private int _buffer;
	private String _picName = null;
	private int _picSize;
	private int _MSS;
	private int _updateFrequency;
	private int _N;
	private DB _db = null;
	
	//Other peer to contact with
	private String _otherPeerName = null;
	private String _otherIP = null;
	private int _otherPortTCP;
	private int _otherPortUDP;
	
	/**
	 * Stops communicating with other peer
	 */
	public void stop() {
		_udpAdapter.close();
		_updater.stop();
		_pictureManager.stop();
		_tcpListener.stop();
		_handler.stop();
	}
	
	/**
	 * Get owner details
	 * @return
	 */
	public Record getOwner() {
		if (_owner == null)
			return null;
		
		return _owner.clone();
	}
	
	/**
	 * Contact with other peer explicitly
	 * @param name
	 * @param ip
	 * @param tcp
	 * @param udp
	 */
	public void addOtherPeerData(String name, String ip, int tcp, int udp) {
		_otherPeerName = name;
		_otherIP = ip;
		_otherPortTCP = tcp;
		_otherPortUDP = udp;
	}
	
	/**
	 * Constructor
	 * @param name
	 * @param ip
	 * @param tcp
	 * @param udp
	 * @param phone
	 * @param email
	 * @param buffer
	 * @param picName
	 * @param picSize
	 * @param mss
	 * @param updateFreq
	 * @param N
	 * @param otherName
	 * @param otherIP
	 * @param otherTCP
	 * @param otherUDP
	 */
	public Peer(String name, String ip, int tcp, int udp, String phone, String email, int buffer, String picName, int picSize, int mss, int updateFreq, int N, String otherName, String otherIP, int otherTCP, int otherUDP) {
		_name = name;
		_ip = ip;
		_portTCP = tcp;
		_portUDP = udp;
		_phone = phone;
		_email = email;
		_buffer = buffer;
		_picName = picName;
		_picSize = picSize;
		_MSS = mss;
		_updateFrequency = updateFreq;
		_N = N;
	
		_otherPeerName = otherName;
		_otherIP = otherIP;
		_otherPortTCP = otherTCP;
		_otherPortUDP = otherUDP;
		
	}
	
	/**
	 * Prepare communication with other peers
	 * @throws IOException
	 */
	public void initialize() throws IOException{
		File file = new File(DB_FILE_NAME);
		
		boolean fileExists = false;
		boolean failed = false;
		
		try {
			if (file.exists()) {
				loadDB();
				fileExists = true;
				_owner = _db.getRecord(_db.get_OwnerName());
				_db.resetTries();
			}
		} catch (IOException ex) {
			Proj350.logger.logError("Error reading existing DB file, new DB was created");
			failed = true;
		} catch (ClassNotFoundException ex) {
			Proj350.logger.logError("Error reading existing DB file, new DB was created");
			failed = true;
		}
		
		if (failed || !fileExists) {
			Date now = Utils.StringToDate(Utils.DateToString(new Date(System.currentTimeMillis())));
			_owner = new Record(Utils.zeroDate(), _email, _ip, now, _name, now, _phone, _picSize, now, _buffer, 1, _portTCP, _portUDP, Utils.zeroDate());
			_db = new DB(_owner.clone());
			addFirstPeer(_otherPeerName, _otherIP, _otherPortTCP, _otherPortUDP);
			
			//Check validity of owner picture file
			copy(new File(_picName), new File(_owner.get_peerName() + ".jpg"));
		}
	}
	
	/**
	 * Adds other peer to the database
	 * @param name
	 * @param ip
	 * @param tcp
	 * @param udp
	 */
	private void addFirstPeer(String name, String ip, int tcp, int udp) {
		Record rec = new Record(name);
		rec.set_ip(ip);
		rec.set_tcp(tcp);
		rec.set_udp(udp);
		
		_db.addRecord(name, rec);
	}
	
	/**
	 * Creates a new database
	 */
	public void createDB() {	
		Date now = Utils.now(); 
		_owner = new Record(Utils.zeroDate(), _email, _ip, now, _name, now, _phone, _picSize, now, _buffer, 1, _portTCP, _portUDP, Utils.zeroDate());
		_db = new DB(_owner);
	}
	
	/**
	 * Deletes the database
	 */
	public void deleteDBFile() {
		File file = new File(DB_FILE_NAME);
		
		if (file.exists())
			file.delete();
	}
	
	/**
	 * Saves DB file to hard drive
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public void saveDB() throws FileNotFoundException, IOException{
		FileOutputStream os = new FileOutputStream(DB_FILE_NAME);
		ObjectOutputStream oos = new ObjectOutputStream(os); 
		oos.writeObject(_db);
		os.close();
	}
	
	/**
	 * Loads DB file from hard drive
	 * @throws FileNotFoundException
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	public void loadDB() throws FileNotFoundException, IOException, ClassNotFoundException{
		FileInputStream is = new FileInputStream(DB_FILE_NAME);
		ObjectInputStream ois = new ObjectInputStream(is);
		Object obj = ois.readObject();
		
		if (obj instanceof DB)
		_db = (DB)obj;
		
		is.close();
	}
	
	// Copies src file to dst file.
    // If the dst file does not exist, it is created
    private void copy(File src, File dst) throws IOException {
        InputStream in = new FileInputStream(src);
        OutputStream out = new FileOutputStream(dst);
    
        // Transfer bytes from in to out
        byte[] buf = new byte[1024];
        int len;
        while ((len = in.read(buf)) > 0) {
            out.write(buf, 0, len);
        }
        in.close();
        out.close();
    }

    /**
     * Updates fields in owner record
     * @param args
     * @throws IOException
     */
    public void UpdateDB(String[] args) throws IOException{
    	int i = 1;
    	
    	Date now = Utils.now();
    	
    	//Analyze the fields to be changed
    	while (i < args.length) {
    		if (args[i].equals("ip"))
    			_owner.set_ip(args[++i]);
    		else if (args[i].equals("tcp"))
    			_owner.set_tcp(Integer.parseInt(args[++i]));
    		else if (args[i].equals("udp"))
    			_owner.set_udp(Integer.parseInt(args[++i]));
    		else if (args[i].equals("phone"))
    			_owner.set_phone(args[++i]);
    		else if (args[i].equals("email"))
    			_owner.set_email(args[++i]);
    		else if (args[i].equals("buffer")) {
    			_buffer = (Integer.parseInt(args[++i]));
    		} else if (args[i].equals("peername")) {
    			_owner.set_peerName(args[++i]);
    		} else if (args[i].equals("pic")) {
    			_picName = args[i+1];
    			copy(new File(args[++i]), new File(_owner.get_peerName() + ".jpg"));
    			_owner.set_picVersion(now);
    		} else if (args[i].equals("picsize")) {
    			_owner.set_picSize(Integer.parseInt(args[++i]));
    		}
 
    		++i;
    	}
    	
    	//Update DB
    	_owner.set_peerVersion(now); 
    	_db.setOwnerName(_owner.get_peerName());
    	_db.addRecord(_owner.get_peerName(), _owner.clone());
    }
    
    /**
     * Gets current picture file name
     * @return
     */
    public String getPictureFileName() {return _picName;}
    
    /**
     * Starts communicating with other peers
     */
	public void start() {
		try {
			
			int padding = 1024; //Extra space for messages in socket
			int timeoutUDP = 2000; //Timeout for sockets
			int maxIdleTimePicture = 4000; //Max idle time for picture manager
			int serverSocketTimeout = 1000; //Server socket timeout
			int tcpListenerTimeOut = 10000; //Timeout for sockets created by the TCP listener
			int maxMessageIdleTime = 4000; //Idle time between messages
			
			//Initialize UDP socket
			SyncUDPSocket udp = new SyncUDPSocket(_portUDP);
			
			_udpAdapter = new UDPAdapter(_owner.get_peerName(), _owner.get_ip(), udp, _MSS + padding, timeoutUDP);
			_udpAdapter.initialize();
			
			//Initialize picture manager object
			_pictureManager = new PictureManager(_db, _N, _MSS, _buffer, maxIdleTimePicture);
			_pictureManager.start();
			
			//Listen to TCP messages
			_tcpListener = new TCPListener(_portTCP, serverSocketTimeout,
					tcpListenerTimeOut, _MSS + padding, maxMessageIdleTime, _db, _pictureManager, _MSS);
			new Thread(_tcpListener, "TCP Listener").start();
			
			//Initialize handler object - handles incoming messages
			_handler = new Handler(_udpAdapter, _db,
					_pictureManager, maxMessageIdleTime, _MSS);
			new Thread(_handler, "Handler").start();
		
			//Initialize updater object
			_updater = new Updater(maxMessageIdleTime, _updateFrequency, _MSS + padding, tcpListenerTimeOut, 
					_udpAdapter, _db, _pictureManager, _MSS, _owner.get_peerName());
			 new Thread(_updater, "Updater").start();
			
		} catch (SocketException ex) {
			Proj350.logger.logError("Error creating UDP socket in port: " + _udpAdapter.getLocalPort());
			Proj350.logger.close();
			System.exit(-1);
		} catch (IOException ex) {
			Proj350.logger.logError("Error creating UDP socket in port: " + _udpAdapter.getLocalPort());
			Proj350.logger.close();
			System.exit(-1);
		}
	}
	
	public void run() {
		start();
	}

	
}
